package com.hulk.ratel.manage.inner;

import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;


import com.google.common.base.Throwables;


import com.hulk.ratel.common.constant.enums.EC;

import com.hulk.ratel.common.exception.NotifyRteException;
import com.hulk.ratel.common.exception.SecurityRteException;
import com.hulk.ratel.common.security.ConsumeSecurityWarper;
import com.hulk.ratel.common.util.DateUtil;
import com.hulk.ratel.common.util.SaltGenerator;
import com.hulk.ratel.common.constant.Constants;
import com.hulk.ratel.manage.bo.AsyncParamRespBO;

import com.hulk.ratel.manage.cache.PartnerKeyCache;
import com.hulk.ratel.manage.cache.SecurityPartnerCache;
import com.hulk.ratel.manage.commonutil.ContentUtil;

import com.hulk.ratel.manage.service.LogTxnPayRecordService;
import com.hulk.ratel.manage.service.NotifyRecordService;
import com.hulk.ratel.persistence.entity.LogTxnPayRecord;
import com.hulk.ratel.persistence.entity.NotifyRecord;
import com.hulk.ratel.persistence.entity.PartnerKey;
import com.hulk.ratel.api.web.dto.AsyncParamRespDTO;
import com.hulk.ratel.api.web.dto.HeadRespDTO;
import com.hulk.ratel.api.web.dto.PayNotifyRespDTO;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Objects;
import java.util.Optional;

/**
 * @Author: hulk
 * @E-mail: 29572320@qq.com
 * @Date: Create in 2018/9/20 下午3:28
 * @Version: 1.0.0
 * @Modify by:
 * Class description
 */
@Slf4j
@AllArgsConstructor
@Component
public class RetryNotifyService {

	//通知次数
	protected static final int MAX_NOTIFY_COUNT = 5;
	protected static final String NOTIFY_SUCCESS = "SUCCESS";

    private NotifyRecordService notifyRecordService;

    private LogTxnPayRecordService logTxnPayRecordService;

    private ContentUtil contentUtil;

    private PartnerKeyCache partnerKeyCache;

    private SecurityPartnerCache securityPartnerCache;

    @Retryable(value = {NotifyRteException.class}, maxAttempts = MAX_NOTIFY_COUNT,  backoff = @Backoff(delay = 2000L, maxDelay =90000, multiplier = 2))
    public void doNotify(AsyncParamRespBO bo, NotifyRecord notify, LogTxnPayRecord record, Long notifyId) {
        log.info("notifyId:{} 交易支付结果,通知前端商户,bo:{} record:{}", notifyId, bo, record);
        try {
            // 下游没有明确响应"SUCCESS"进行重复通知。

			notifyRecordService.updateRetryNotifyCount(notifyId);


            AsyncParamRespDTO respDTO = getNotifyParam(record);

            String result =  HttpUtil.post(notify.getNotifyUrl(), respDTO.toParamMap());

            log.info("异步通知返回:{}",result);
            if (!NOTIFY_SUCCESS.equalsIgnoreCase(result)) {
                throw new NotifyRteException(EC.E_300001);
            }
        }catch (NotifyRteException e){
            throw e;
        } catch (Exception e) {
            log.error(" 交易支付结果异步通知异常:{}",  Throwables.getStackTraceAsString(e));
        }
    }


    @Recover
    public void recover(NotifyRteException ex) {
        log.error("交易支付结果异步通知前端失败！",  ex);
    }




    private AsyncParamRespDTO getNotifyParam(LogTxnPayRecord record){
        //合作方密钥查询
        PartnerKey partnerKey = Optional.ofNullable(partnerKeyCache.findPartnerKey(Objects.requireNonNull(record.getPartnerNo())))
                .orElseThrow(()->new SecurityRteException( EC.E_100002));

        String partnerPublicKeyStr = partnerKey.getPartnerPublickey();
        String platformPrivateKeyStr = partnerKey.getPlatformPrivatekey();
        String partnerPublicKeySuffix = partnerKey.getPartnerPublickeySuffix();
        String platformPrivateKeySuffix =partnerKey.getPlatformPrivatekeySuffix();
        String keypathType =  partnerKey.getKeypathType();

        //生成随机AESkey
        String partnerAESKey = SaltGenerator.generateSalt();

        //获取合作方公钥
        PublicKey publicKeyPartner = Optional.ofNullable(securityPartnerCache.findPublicKey(record.getPartnerNo(), partnerPublicKeyStr, partnerPublicKeySuffix, keypathType))
                .orElseThrow(() -> new SecurityRteException( EC.E_100010));
        //获取平台私钥
        PrivateKey privateKeyPlatform = Optional.ofNullable(securityPartnerCache.findPrivateKey(record.getPartnerNo(),platformPrivateKeyStr,platformPrivateKeySuffix, keypathType))
                .orElseThrow(() -> new SecurityRteException(EC.E_100020));

        //aes加密报文
        PayNotifyRespDTO payNotifyRespDTO = new PayNotifyRespDTO();
        HeadRespDTO headResp = new HeadRespDTO();
        headResp.setVersion(Constants.VERSION);
        headResp.setCharset(StandardCharsets.UTF_8.name());
        headResp.setPartnerNo(record.getPartnerNo());
        headResp.setTxnCode(record.getTxnCode());
        headResp.setTraceId(record.getTraceId());
        headResp.setRespDate(DateUtil.getDateyyyyMMdd());
        headResp.setRespTime(DateUtil.getDateyyyyMMddHHmmss());
        headResp.setRespCode(EC.S_0.getErrCode());
        headResp.setRespMsg(EC.S_0.getErrDesc());
        payNotifyRespDTO.setHeadRespDTO(headResp);

        //返回报文
        String respJsonStr = JSONUtil.toJsonStr(payNotifyRespDTO);

        //加密
        String encryptData =  ConsumeSecurityWarper.getCipherB64PlainTextByPartnerAESKey(respJsonStr,partnerAESKey);

        //公钥加密AESkey
        String encryptKey = ConsumeSecurityWarper.getCipherB64AESKeyByPlatformPublicKey(partnerAESKey,publicKeyPartner);

        //私钥签名
        String signature = ConsumeSecurityWarper.getSignB64PlainTextByPartnerPrivateKey(respJsonStr , privateKeyPlatform);

        AsyncParamRespDTO asyncParamRespDTO = new AsyncParamRespDTO();
        asyncParamRespDTO.setEncryptData(encryptData);
        asyncParamRespDTO.setEncryptKey(encryptKey);
        asyncParamRespDTO.setSignature(signature);
        asyncParamRespDTO.setTraceId(record.getTraceId());
        return asyncParamRespDTO;
    }


}
